package raft.storage;

import java.io.Serializable;

/**
 * Raft日志条目
 * 
 * <p>表示Raft算法中的一个日志条目，包含了客户端的命令以及元数据信息。
 * 日志条目是Raft算法中状态机复制的基本单位。</p>
 * 
 * <p><b>关键特性：</b></p>
 * <ul>
 *   <li>不可变性：所有字段都是final的，确保日志条目一旦创建就不可修改</li>
 *   <li>可序列化：实现了Serializable接口，支持网络传输和持久化存储</li>
 *   <li>唯一标识：通过term和index的组合可以唯一标识一个日志条目</li>
 * </ul>
 * 
 * <p><b>Raft日志一致性保证：</b></p>
 * <ul>
 *   <li>如果两个条目有相同的index和term，则它们包含相同的command</li>
 *   <li>如果两个条目有相同的index和term，则它们之前的所有条目都相同</li>
 * </ul>
 * 
 * @author Raft Implementation Team
 * @version 1.0
 * @since 1.0
 */
public class LogEntry implements Serializable {
    
    /** 序列化版本号 */
    private static final long serialVersionUID = 1L;
    
    /** 任期号 - 创建此日志条目时的Leader任期号 */
    private final int term;
    
    /** 日志索引 - 此条目在日志中的位置（从1开始） */
    private final int index;
    
    /** 客户端命令 - 要应用到状态机的具体操作 */
    private final String command;
    
    /** 创建时间戳 - 用于调试和监控（非Raft算法必需） */
    private final long timestamp;

    /**
     * 构造一个新的日志条目
     * 
     * @param term 任期号，必须是正整数
     * @param index 日志索引，必须是正整数且单调递增
     * @param command 客户端命令，不能为null（可以为空字符串）
     * @throws IllegalArgumentException 如果参数不合法
     */
    public LogEntry(int term, int index, String command) {
        if (term < 0) {
            throw new IllegalArgumentException("任期号不能为负数: " + term);
        }
        if (index < 0) {
            throw new IllegalArgumentException("日志索引不能为负数: " + index);
        }
        if (command == null) {
            throw new IllegalArgumentException("命令不能为null");
        }
        
        this.term = term;
        this.index = index;
        this.command = command;
        this.timestamp = System.currentTimeMillis();
    }

    /**
     * 获取任期号
     * 
     * @return 创建此日志条目时的Leader任期号
     */
    public int getTerm() {
        return term;
    }

    /**
     * 获取日志索引
     * 
     * @return 此条目在日志数组中的位置（从1开始）
     */
    public int getIndex() {
        return index;
    }

    /**
     * 获取客户端命令
     * 
     * @return 要应用到状态机的命令字符串
     */
    public String getCommand() {
        return command;
    }

    /**
     * 获取创建时间戳
     * 
     * @return 日志条目创建时的系统时间戳（毫秒）
     */
    public long getTimestamp() {
        return timestamp;
    }

    /**
     * 返回日志条目的字符串表示
     * 
     * @return 包含所有字段信息的格式化字符串
     */
    @Override
    public String toString() {
        return "LogEntry{" +
                "term=" + term +
                ", index=" + index +
                ", command='" + command + '\'' +
                ", timestamp=" + timestamp +
                '}';
    }

    /**
     * 比较两个日志条目是否相等
     * 
     * <p>根据Raft算法的要求，两个日志条目相等当且仅当它们的term、index和command都相同。
     * 注意：timestamp不参与相等性比较，因为它不是Raft算法的核心数据。</p>
     * 
     * @param o 要比较的对象
     * @return 如果两个日志条目相等则返回true，否则返回false
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        LogEntry logEntry = (LogEntry) o;
        return term == logEntry.term &&
                index == logEntry.index &&
                command.equals(logEntry.command);
    }

    /**
     * 计算日志条目的哈希码
     * 
     * <p>哈希码基于term、index和command计算，与equals方法保持一致。</p>
     * 
     * @return 日志条目的哈希码
     */
    @Override
    public int hashCode() {
        return term + index + command.hashCode();
    }
} 